package schedule;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;

import afsjava.Operation;
import afsjava.Tjob;
import afsjava.Workcenter;


/**
 * <p> Class Timed Schedule </p>
 * <p> The Schedule class provides the methods required to generate a valid schedule for Lekin <br>
 * A schedule is a multi-dimensional array which contains in its i,l,k position the job processed k-th on the l-th machine
 * of the i-th workstation</p>
 * In addition a TimedSchedule contains all Lekin performance indicators:
 * Makespan, max Tardiness, number of Tardy jobs, Total and Total Weighted Flow Time
 * and Total and Total Weighted Tardiness.</p>
 * @author Gonzalo Mejia
 * @version 3.0
 */


public class TimedSchedule {

	/**
	 * the array of jobs
	 */
	protected Tjob[] jobArray;


	/**
	 * the array of workcenters
	 */
	protected Workcenter[] wkcArray;
	
	/**
	 * jobTimes[j] contains the end time of the last scheduled operation of the j-th job
	 */
	protected int jobTimes[];

	/**
	 * machineTimes[i][l] contains the earliest time that machine l-th
	 * of workcenter i becomes available
	 * First index = workcenter. Second index = machine
	 */	
	protected int machineTimes[][];

	/**
	 * Stores the index of the last scheduled operation of each job
	 */
	private int currentJobOperation[];
	
	
	/**
	 * machineCurrentSetups[i][l] contains the last setup (status) of machine l of workstation i
	 * First index = workcenter. Second index = machine
	 */	
	protected char machineCurrentSetups[][];

	/**
	 * This is an array of W workstations, Mi machines per workstation.
	 * Stores the operation scheduled on the i,j position
	 */	
	protected ArrayList<TimedOperation> timedGantt[][];

	// stores Cj for each job
	protected int[] flowTimes;

	//stores the tardiness of each job max(Cj-dj,0)
	protected int[] jobTardiness;

	//	stores the 1 if the job is tardy; 0 otherwise
	protected int[] tardyJobs;

	protected int makespan; //max Cj

	protected int maxTardiness; //max Tj; 

	protected int totalFlowTime; // sum Cj

	protected int totalTardiness; // sum Tj

	protected int totalWeightedFlowTime; // sum wjCj

	protected int totalWeightedTardiness; // sum wjTj

	protected int numberOfTardyJobs;

	protected boolean valid;

	/**
	 * Constructs an empty Timed Schedule
	 * @param wkcArray
	 * @param jobArray
	 */
	public TimedSchedule(Workcenter[] wkcArray, Tjob[] jobArray) {

		// initialization step
		this.jobArray = jobArray;
		this.wkcArray = wkcArray;
		
		jobTimes = new int[Tjob.getNumberOfJobs()];

		jobTardiness = new int[Tjob.getNumberOfJobs()];
		tardyJobs = new int[Tjob.getNumberOfJobs()];
		flowTimes = new int[Tjob.getNumberOfJobs()];
		machineCurrentSetups = new char[Workcenter.getNumberOfWorkcenters()][];
		machineTimes = new int[Workcenter.getNumberOfWorkcenters()][];
		currentJobOperation = new int[Tjob.getNumberOfJobs()];

		timedGantt = new ArrayList[Workcenter.getNumberOfWorkcenters()][];

		for (int j =0; j < Tjob.getNumberOfJobs(); j++){	
			jobTimes[j] = jobArray[j].getRelease();
			flowTimes[j] = jobTimes[j];
		}

		for (int i = 0; i < machineTimes.length; i++) {

			machineTimes[i] = new int[wkcArray[i].getNumberOfMachines()];
			machineCurrentSetups[i]= new char [wkcArray[i].getNumberOfMachines()];

			for (int l=0 ; l < wkcArray[i].getNumberOfMachines(); l++){
				machineTimes[i][l] = wkcArray[i].getMachine(l).getAvailableDate();
				machineCurrentSetups[i][l] = wkcArray[i].getMachine(l).getStatus();
			}             

			timedGantt[i] = new ArrayList[wkcArray[i].getNumberOfMachines()];
			for (int j=0; j < timedGantt[i].length;j++){

				timedGantt[i][j] = new ArrayList<TimedOperation>();

			}// for

		} //for


	} // constructor
	
	/**
	 * constructs a timed schedule from an untimed schedule. Calculates the start and end times
	 * of all job operations. Also calculates the Lekin performance indicators.
	 * This method checks precedence constraints before adding any operation to a schedule
	 * @param jobArray
	 * @param wkcArray
	 * @param schedule
	 * @throws Exception
	 */
	public TimedSchedule(Workcenter[] wkcArray, Tjob[] jobArray, Schedule schedule){
		
		//initialization step
		this.valid = true;
		this.jobArray = jobArray;
		this.wkcArray = wkcArray;
		
		this.jobTimes = new int[Tjob.getNumberOfJobs()];

		this.jobTardiness = new int[Tjob.getNumberOfJobs()];
		this.tardyJobs = new int[Tjob.getNumberOfJobs()];
		this.flowTimes = new int[Tjob.getNumberOfJobs()];
		this.machineCurrentSetups = new char[Workcenter.getNumberOfWorkcenters()][];
		this.machineTimes = new int[Workcenter.getNumberOfWorkcenters()][];
		this.currentJobOperation = new int[Tjob.getNumberOfJobs()];

		this.timedGantt = new ArrayList[Workcenter.getNumberOfWorkcenters()][];

		for (int j =0; j < Tjob.getNumberOfJobs(); j++){	
			jobTimes[j] = jobArray[j].getRelease();
			flowTimes[j] = jobTimes[j];
		}

		for (int i = 0; i < machineTimes.length; i++) {

			machineTimes[i] = new int[wkcArray[i].getNumberOfMachines()];
			machineCurrentSetups[i]= new char [wkcArray[i].getNumberOfMachines()];

			for (int l=0 ; l < wkcArray[i].getNumberOfMachines(); l++){
				machineTimes[i][l] = wkcArray[i].getMachine(l).getAvailableDate();
				machineCurrentSetups[i][l] = wkcArray[i].getMachine(l).getStatus();
			}             

			timedGantt[i] = new ArrayList[wkcArray[i].getNumberOfMachines()];
			for (int j=0; j < timedGantt[i].length;j++){

				timedGantt[i][j] = new ArrayList<TimedOperation>();

			}// for

		} //for
		
		currentJobOperation = new int[Tjob.getNumberOfJobs()];
		int currentOperationOnMachine[][] = new int[Workcenter.getNumberOfWorkcenters()][];
		for (int i=0; i< Workcenter.getNumberOfWorkcenters();i++){
			currentOperationOnMachine[i] = new int[wkcArray[i].getNumberOfMachines()];
		}
		int numberOfWkcenters = Workcenter.getNumberOfWorkcenters();
		
		try {
			int numberOfUnscheduledOperations = schedule.getNumberOfOperations();//Tjob.getTotalNumberOfOperations();
			int numberOfScheduledOperations = 0;
			int k =0; // counter for operations not yet scheduled
			
			//while there are operations to schedule
			while (numberOfUnscheduledOperations  > 0){
				
				
				for (int i=0; i < numberOfWkcenters;i++ ){

					int numberOfMachines = wkcArray[i].getNumberOfMachines();
					
					for (int l=0; l< numberOfMachines; l++ ){

						//returns the position of the machine (number of scheduled jobs)
						int currentMachinePosition = currentOperationOnMachine[i][l];
						
						if (schedule.getGantt(i,l).size() > currentMachinePosition){ //if there are more jobs to schedule
						
							//returns the currentMachinePosition-th job of the j-th machine of the i-th workcenter
							int jobNumber = schedule.getGantt(i,l,currentMachinePosition);

							//returns the operation that is ready to be scheduled
							int currentJobOpnIndex = currentJobOperation[jobNumber];

							//returns the workstation where the current operation of "jobNumber" is performed
							int currentWorkstationNumber= jobArray[jobNumber].getRoute()[currentJobOpnIndex].getWorkcenterNumber();

							if (i == currentWorkstationNumber){ //the job can be scheduled
								
								numberOfScheduledOperations++; // counts how many operations have been scheduled
								
								TimedOperation timedOperation = new TimedOperation(jobNumber, i, l);
						
								//calculates the earliest time that the job "jobNumber" can be scheduled
								int time = Math.max(machineTimes[i][l], jobTimes[jobNumber]);
								timedOperation.setSetupStartTime(time);							
								int rowIndex, colIndex; // indices sequence dependent setup time
													
								rowIndex = machineCurrentSetups[i][l] - 65;		//current machine setup		
								colIndex = jobArray[jobNumber].getStatus(currentJobOpnIndex) - 65; //next machine setup
								
								int setupTime = wkcArray[i].getSetup(rowIndex, colIndex);
								time = time + setupTime;
								
								timedOperation.setSetupEndTime(time);															
								timedOperation.setStartTime(time); //operation start time
															
								int processTime = jobArray[jobNumber].getProcessTime(currentJobOpnIndex);
								timedOperation.setEndTime(time + processTime); //operation end time
								
								//updates job and machine times and setup status
								machineTimes[i][l]= time + processTime;
								jobTimes[jobNumber] = time + processTime;
								machineCurrentSetups[i][l] = jobArray[jobNumber].getStatus(currentJobOpnIndex);
								
								//adds operation
								timedGantt[i][l].add(timedOperation);
														
								System.out.println("Workcenter " + i + " Machine " + l + " job " + jobNumber + " time " + jobTimes[jobNumber]);
								
								//updates the current operation indices
								currentOperationOnMachine[i][l]++; //goes on to the next machine
								currentJobOperation[jobNumber]++; // goes on to the next operation
								numberOfUnscheduledOperations--;
								k=0;
								if (numberOfUnscheduledOperations==0){
									break;
								}
							}
							else {
								
								k++; // no operation was scheduled
								if (k == numberOfUnscheduledOperations){
									
									numberOfUnscheduledOperations=0;
									valid = false;
									throw new Exception();
									
								}// if
								
							}// else
							
						} // if
						
					}//for
					if (numberOfUnscheduledOperations==0){
						break;
					}
				} // for i

			}// while
			if (valid == true){
				this.calcPerformance();
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("Invalid Schedule. The program will terminate");
			//e.printStackTrace();
			System.exit(1);
		}

	}//constructor



	/**
	 * @return the machineCurrentSetups
	 */
	public char[][] getMachineCurrentSetups() {
		return machineCurrentSetups;
	}


	/**
	 * @param machineCurrentSetups the machineCurrentSetups to set
	 */
	public void setMachineCurrentSetups(char[][] machineCurrentSetups) {
		this.machineCurrentSetups = machineCurrentSetups;
	}




	/**
	 * @return
	 */
	public int[] getFlowTimes() {
		return flowTimes;
	}


	/**
	 * @return
	 */
	public int[] getJobTardiness() {
		return jobTardiness;
	}


	/**
	 * @return
	 */
	public int[] getJobTimes() {
		return jobTimes;
	}


	/**
	 * @return
	 */
	public int[][] getMachineTimes() {
		return machineTimes;
	}


	/**
	 * @return
	 */
	public int getMakespan() {
		return makespan;
	}


	/**
	 * @return
	 */
	public int getMaxTardiness() {
		return maxTardiness;
	}


	/**
	 * @return
	 */
	public int getNumberOfTardyJobs() {
		return numberOfTardyJobs;
	}


	/**
	 * @return
	 */
	public int[] getTardyJobs() {
		return tardyJobs;
	}


	/**
	 * @return
	 */
	public ArrayList<TimedOperation>[][] getTimedGantt() {
		return timedGantt;
	}


	/**
	 * @return
	 */
	public int getTotalFlowTime() {
		return totalFlowTime;
	}


	/**
	 * @return
	 */
	public int getTotalTardiness() {
		return totalTardiness;
	}


	/**
	 * @return
	 */
	public int getTotalWeightedFlowTime() {
		return totalWeightedFlowTime;
	}


	/**
	 * @return totalWeightedTardiness
	 */
	public int getTotalWeightedTardiness() {
		return totalWeightedTardiness;
	}


	/** returns the boolean value of "valid"
	 * @return valid
	 */
	public boolean isValid() {
		return valid;
	}


	/**
	 * @param flowTimes
	 */
	public void setFlowTimes(int[] flowTimes) {
		this.flowTimes = flowTimes;
	}

	/**
	 * @param jobTardiness
	 */
	public void setJobTardiness(int[] jobTardiness) {
		this.jobTardiness = jobTardiness;
	}

	/**
	 * @param jobTimes
	 */
	public void setJobTimes(int[] jobTimes) {
		this.jobTimes = jobTimes;
	}

	/**
	 * @param machineTimes
	 */
	public void setMachineTimes(int[][] machineTimes) {
		this.machineTimes = machineTimes;
	}
	
	/**
	 * @param i
	 * @param l
	 * @param time
	 */
	public void setMachineTimes(int i, int l, int time){
		this.machineTimes[i][l] = time;
		
	}


	/**
	 * @param makespan
	 */
	public void setMakespan(int makespan) {
		this.makespan = makespan;
	}


	/**
	 * @param maxTardiness
	 */
	public void setMaxTardiness(int maxTardiness) {
		this.maxTardiness = maxTardiness;
	}


	/**
	 * @param numberOfTardyJobs
	 */
	public void setNumberOfTardyJobs(int numberOfTardyJobs) {
		this.numberOfTardyJobs = numberOfTardyJobs;
	}


	/**
	 * @param tardyJobs
	 */
	public void setTardyJobs(int[] tardyJobs) {
		this.tardyJobs = tardyJobs;
	}


	/**
	 * @param timedGantt
	 */
	public void setTimedGantt(ArrayList<TimedOperation>[][] timedGantt) {
		this.timedGantt = timedGantt;
	}

	/**
	 * @param totalFlowTime
	 */
	public void setTotalFlowTime(int totalFlowTime) {
		this.totalFlowTime = totalFlowTime;
	}

	/**
	 * @param totalTardiness
	 */
	public void setTotalTardiness(int totalTardiness) {
		this.totalTardiness = totalTardiness;
	}

	/**
	 * @param totalWeightedFlowTime
	 */
	public void setTotalWeightedFlowTime(int totalWeightedFlowTime) {
		this.totalWeightedFlowTime = totalWeightedFlowTime;
	}

	/**
	 * @param totalWeightedTardiness
	 */
	public void setTotalWeightedTardiness(int totalWeightedTardiness) {
		this.totalWeightedTardiness = totalWeightedTardiness;
	}

	/**
	 * @param valid
	 */
	public void setValid(boolean valid) {
		this.valid = valid;
	}

	/**
	 * @param jobNumber
	 * @param wksNumber
	 * @param mchNumber
	 */
	public void addOperation(int jobNumber, int wksNumber, int mchNumber){

		int currentJobOpnIndex = currentJobOperation[jobNumber];
		TimedOperation timedOperation = new TimedOperation(jobNumber, wksNumber, mchNumber);
		
		//calculates the earliest time that the job "jobNumber" can be scheduled
		int time = Math.max(machineTimes[wksNumber][mchNumber], jobTimes[jobNumber]);
		timedOperation.setSetupStartTime(time);							
		int rowIndex, colIndex; // indices sequence dependent setup time
							
		rowIndex = machineCurrentSetups[wksNumber][mchNumber] - 65;		//current machine setup		
		colIndex = jobArray[jobNumber].getStatus(currentJobOpnIndex) - 65; //next machine setup
		
		int setupTime = wkcArray[wksNumber].getSetup(rowIndex, colIndex);
		time = time + setupTime;
		
		timedOperation.setSetupEndTime(time);															
		timedOperation.setStartTime(time); //operation start time
									
		int processTime = jobArray[jobNumber].getProcessTime(currentJobOpnIndex);
		timedOperation.setEndTime(time + processTime); //operation end time
		
		time = timedOperation.getEndTime();
		//update job and machine times
		this.updateTimes(jobNumber, wksNumber, mchNumber, time);

		//adds the operation
		timedGantt[wksNumber][mchNumber].add(timedOperation);

		System.out.println("Job " + jobNumber + " Workcenter " + wksNumber + " Machine " + mchNumber +  " Start time " + 
				timedOperation.getStartTime() + " End time " + timedOperation.getEndTime());


	}


	/**
	 * Adds a timed Operation to the timedGantt. Puts the operation at the end of the list of the wksNumber-th workstation 
	 * of the mchNumber-th machine
	 * @param timedOperation
	 * @param wksNumber
	 * @param mchNumber
	 */
	public void addOperation(TimedOperation timedOperation, int wksNumber, int mchNumber){

		int time = timedOperation.getEndTime(); // end time of the operation
		int jobNumber = timedOperation.getJobNumber();
		
		timedOperation.setOperationTimes(jobNumber, wksNumber, mchNumber, this);

		//update job and machine times
		this.updateTimes(jobNumber, wksNumber, mchNumber, time);

		//adds operation
		timedGantt[wksNumber][mchNumber].add(timedOperation);

		System.out.println("Job " + jobNumber + " Workcenter " + wksNumber + " Machine " + mchNumber +  " Start time " + 
				timedOperation.getStartTime() + " End time " + timedOperation.getEndTime());

	}
	
	/**
	 * Adds a job operation to a TimedSchedule object. This method adds the operation of the
	 * jobNumber-th job to the machNumber-th machine of the wksNumber-th workstation.<br>
	 * Only works if all job precedences have been scheduled; otherwise throws an exception.<br>
	 * This methods also calculates the earliest setup start time, the setup end time, the start and the end 
	 * of the scheduled operation
	 * @param jobNumber
	 * @param wksNumber
	 * @param mchNumber
	 * @throws Exception
	 */
	public void addToTimedSchedule(int jobNumber, int wksNumber, int mchNumber){

		try {

			//returns the index of operation of the job "jobNumber" that is ready to be scheduled
			int currentJobOpnIndex = currentJobOperation[jobNumber];

			//returns the workstation where the current operation of "jobNumber" must be performed
			int currentWksNumber = jobArray[jobNumber].getRoute()[currentJobOpnIndex].getWorkcenterNumber();

			if (wksNumber == currentWksNumber){ //the job can be scheduled

				this.addOperation(jobNumber, wksNumber, mchNumber);
				
				//updates the current operation indices
				currentJobOperation[jobNumber]++;
				this.calcPerformance(jobNumber);

			}
			else {
				valid = false;
				throw new Exception();				
			}// else              

		} catch (Exception e) {
			System.out.println("Error adding to a Timed Schedule");
			System.exit(1);
		}		

	}
	

	/**
	 * @param jobNumber
	 * @param wksNumber
	 * @param mchNumber
	 * @param time
	 */
	public void updateTimes(int jobNumber, int wksNumber, int mchNumber, int time){
		
		//Subtracts these values before the update of the indicators
		totalFlowTime = totalFlowTime -jobTimes[jobNumber];
		totalWeightedFlowTime = totalWeightedFlowTime -jobArray[jobNumber].getWeight()*jobTimes[jobNumber];
		totalTardiness = totalTardiness -jobTardiness[jobNumber];
		totalWeightedTardiness = totalWeightedTardiness -jobArray[jobNumber].getWeight()*jobTardiness[jobNumber];
		numberOfTardyJobs = numberOfTardyJobs - tardyJobs[jobNumber];

		//updates job and machine clock times
		machineTimes[wksNumber][mchNumber] = time;
		jobTimes[jobNumber] = time;

		//updates the current operation indices
		this.calcPerformance(jobNumber);
	}

	/** Calculates the performance indicators of a schedule
	 * Makespan, max Tardiness, number of Tardy jobs, Total and Total Weighted Flow Time
	 * and Total and Total Weighted Tardiness.
	 * @param jobArray
	 * @throws Exception
	 */
	public void calcPerformance(){

		//		finds the performance indicators
		makespan=0;
		maxTardiness=0;
		numberOfTardyJobs=0;
		totalFlowTime=0;
		totalWeightedFlowTime=0;
		totalTardiness=0;
		totalWeightedTardiness=0;

		try {
			for (int j=0; j < Tjob.getNumberOfJobs();j++ ){

				int weight = jobArray[j].getWeight();
				flowTimes[j] = jobTimes[j];
				if (jobTimes[j] > makespan){
					makespan = jobTimes[j];
				}

				totalFlowTime = totalFlowTime + flowTimes[j];
				totalWeightedFlowTime = totalWeightedFlowTime + weight*flowTimes[j];

				int dueDate = jobArray[j].getDue();
				jobTardiness[j]= Math.max(jobTimes[j]-dueDate, 0);

				if (jobTardiness[j] > maxTardiness){
					maxTardiness = jobTardiness[j];
				}

				totalTardiness = totalTardiness + jobTardiness[j];
				totalWeightedTardiness = totalWeightedTardiness + weight*jobTardiness[j];

				if (jobTardiness[j] > 0){			
					tardyJobs[j]=1;
					numberOfTardyJobs++;
				}// if

			}// for
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("Error calculating performance");
			System.exit(1);
		}

	}

	/** Calculates the performance indicators of a schedule when a new job is scheduled.
	 * Makespan, max Tardiness, number of Tardy jobs, Total and Total Weighted Flow Time
	 * and Total and Total Weighted Tardiness.
	 * @param jobArray
	 * @throws Exception
	 */
	public void calcPerformance(int jobNumber){

		//finds the performance indicators

		int weight = jobArray[jobNumber].getWeight();
		int release = jobArray[jobNumber].getRelease();

		try {
			flowTimes[jobNumber] = jobTimes[jobNumber];//-release;
			if (jobTimes[jobNumber] > makespan){
				makespan = jobTimes[jobNumber];
			}

			totalFlowTime = totalFlowTime + flowTimes[jobNumber];
			totalWeightedFlowTime = totalWeightedFlowTime + weight*flowTimes[jobNumber];

			int dueDate = jobArray[jobNumber].getDue();
			jobTardiness[jobNumber]= Math.max(jobTimes[jobNumber]-dueDate, 0);

			if (jobTardiness[jobNumber] > maxTardiness){
				maxTardiness = jobTardiness[jobNumber];
			}

			totalTardiness = totalTardiness + jobTardiness[jobNumber];
			totalWeightedTardiness = totalWeightedTardiness + weight*jobTardiness[jobNumber];

			if (jobTardiness[jobNumber] > 0){			
				tardyJobs[jobNumber]=1;
				numberOfTardyJobs++;//=numberOfTardyJobs + tardyJobs[jobNumber];
			}// if
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("Error calculating performance");
			System.exit(1);
		}

	}

	/**
	 * Prints the performance indicators to the console
	 */
	public void printPerformance(){

		if (valid == true) {
			System.out.println();
			System.out.println("Makespan " + makespan);
			System.out.println("Max Tardiness " + maxTardiness);
			System.out.println("Tardy Jobs " + numberOfTardyJobs); 
			System.out.println("Total Flow Time " + totalFlowTime); 
			System.out.println("Total Tardiness " + totalTardiness); 
			System.out.println("Total Weighted Flow Time " + totalWeightedFlowTime); 
			System.out.println("Total Weighted Tardiness " + totalWeightedTardiness);
			System.out.println();
		}
		else {
			System.out.println("Error printing performance");
		}
	}

	/**
	 * The current timed scheduled is written to the _user.seq file in Lekin.<br>
	 *
	 */
	public void printToLekin(){

		File outFile = new File("_user.seq");

		try {

			BufferedWriter out = new BufferedWriter(new FileWriter(outFile));
			out.write("Schedule \t \t AFS Library");
			out.newLine();

			for (int i = 0; i < timedGantt.length; i++) {

				for (int j = 0; j < timedGantt[i].length; j++) {

					out.write("Machine \t" + i + "." +j);
					out.newLine();

					for (int k = 0; k < timedGantt[i][j].size(); k++) {

						Integer jobNumber = (Integer)timedGantt[i][j].get(k).getJobNumber();
						out.write("Oper: \t" + jobNumber.intValue());
						out.newLine();
					} // for k

				} //for j
			} // for i
			out.close();
		} // try


		catch (IOException e){

			System.out.println("Error writing to Lekin");
			System.exit(1);

		}
	}
	
	

}// class
